package newcrunch.gui;

import diff.viewer.DiffPane;
import java.awt.Point;
import java.awt.event.*;
import java.io.IOException;
import java.util.List;
import javax.swing.*;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import javax.xml.parsers.ParserConfigurationException;
import newcrunch.grader.MarkingScheme;
import newcrunch.grader.ModificationManager;
import newcrunch.grader.SubmissionInfo;
import newcrunch.gui.multispantable.AttributiveCellTableModel;
import newcrunch.gui.multispantable.ColoredCell;
import newcrunch.gui.multispantable.TableCellListener;
import newcrunch.gui.tree.CustomTree;
import newcrunch.util.DisplayHelper;
import newcrunch.util.Logging;
import newcrunch.util.Misc;
import newcrunch.util.PropertyLoader;
import org.netbeans.swing.tabcontrol.TabData;
import org.netbeans.swing.tabcontrol.TabDataModel;
import org.netbeans.swing.tabcontrol.TabDisplayer;
import org.netbeans.swing.tabcontrol.TabbedContainer;
import org.netbeans.swing.tabcontrol.event.TabActionEvent;
import org.xml.sax.SAXException;

/**
 * @author WL
 */
public class RightPanel extends TabbedContainer implements ActionListener
{
    private CustomTree classList;
    private JPopupMenu menu;
    private JMenuItem closeItem;
    private JMenuItem closeAllItem;
    private JMenuItem closeOthersItem;
    private JSeparator separator;
    private JMenuItem propsItem;
    private JMenuItem diffItem;
    private Point popupLoc;
    private AttributiveCellTableModel markingSchemeModel;
    private List<String> inputTestData;
    private List<String> outputTestData;
    private static boolean displayTotal = "true".equalsIgnoreCase(PropertyLoader.getStringProperty("displayTotal"));
    private ModificationManager modManager = new ModificationManager();
    
    public RightPanel()
    {
        super(TabDisplayer.TYPE_EDITOR);
        setActive(true);
        
        addActionListener(this);
        
        addPopup();
        
        //display an empty editor by default
        loadEmptyEditor();
    }
    
    public void setMarkingScheme(String val) 
    {
        try {
            markingSchemeModel = MarkingScheme.generateModel(val, displayTotal);
        }
        catch (SAXException ex) {
            Logging.log(ex);
        }
        catch (IOException ex) {
            Logging.log(ex);
        }
        catch (ParserConfigurationException ex) {
            Logging.log(ex);
        }
    }
    
    private void addPopup()
    {
        menu = new JPopupMenu();
        closeItem = new JMenuItem("Close");
        closeAllItem = new JMenuItem("Close All");
        closeOthersItem = new JMenuItem("Close Others");
        separator = new JSeparator();
        diffItem = new JMenuItem("Compare with Original");
        propsItem = new JMenuItem("Properties");
        
        closeItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_W, InputEvent.CTRL_DOWN_MASK));
        closeItem.setMnemonic(KeyEvent.VK_C);
        closeAllItem.setMnemonic(KeyEvent.VK_A);
        closeOthersItem.setMnemonic(KeyEvent.VK_O);
        diffItem.setMnemonic(KeyEvent.VK_M);
        propsItem.setMnemonic(KeyEvent.VK_P);
        
        menu.add(closeItem);
        menu.add(closeAllItem);
        menu.add(closeOthersItem);
        menu.add(diffItem);
        menu.add(separator);
        menu.add(propsItem);
                
        addMouseListener(new MouseAdapter(){
            @Override
            public void mouseClicked(MouseEvent evt)
            {
                //evt.getButton() == 0 fixes weird bug in 1.7.0_09 whereby the 
                //right click event can generate a 0.
                if (evt.getButton() == MouseEvent.BUTTON3 || evt.getButton() == 0)
                {
                    Point point = RightPanel.this.getMousePosition();
                    menu.show(RightPanel.this, point.x, point.y);
                    popupLoc = point;
                }
                else
                    menu.setVisible(false);
            }
        });
        
        menu.addPopupMenuListener(new PopupMenuListener(){
            @Override
            public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
               Point point = RightPanel.this.getMousePosition();
               int index = RightPanel.this.tabForCoordinate(point);
               if (index >= 0)
               {
                   closeItem.setEnabled(true);
                   closeOthersItem.setEnabled(true);
                   propsItem.setEnabled(true);
                   
                   RightTab pane = (RightTab)(RightPanel.this.getModel().getTab(index).getComponent());
                   diffItem.setEnabled(modManager.getOldText(pane) != null);
               }
               else
               {
                   closeItem.setEnabled(false);
                   closeOthersItem.setEnabled(false);
                   propsItem.setEnabled(false);
                   diffItem.setEnabled(false);
               }
               closeAllItem.setEnabled(RightPanel.this.getModel().size() > 0);
            }

            @Override
            public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {}

            @Override
            public void popupMenuCanceled(PopupMenuEvent e) {}
            
        });
        
        RightPanelPopupListener listener = new RightPanelPopupListener();
        closeItem.addActionListener(listener);
        closeAllItem.addActionListener(listener);
        closeOthersItem.addActionListener(listener);
        propsItem.addActionListener(listener);
        diffItem.addActionListener(listener);
    }
    
    private void loadEmptyEditor()
    {
        RightTab tab = new RightTab();
        
        if (classList != null)
            tab.configureEditorListener(classList);
        
        addTab(tab, 0, "Untitled", "Untitled", null);
        
        //set the focus to it
        getSelectionModel().setSelectedIndex(0);
    }
    
    public void setInputTestData(List<String> input)
    {
        inputTestData = input;
    }
    
    public void setOutputTestData(List<String> output)
    {
        outputTestData = output;
    }
            
    public void linkClassList(CustomTree tree)
    {
        this.classList = tree;
    }
    
    public boolean hasModifiedTab()
    {
        return modManager.hasModifiedTab();
    }
    
    /**
     * Creates and displays a new tab based on the given information.
     * @param submissionInfo
     * @param text
     * @param comment
     * @param compilerOutput
     * @return the created tab
     */
    public RightTab addSubmission(SubmissionInfo submissionInfo, final String text, String comment,
            String compilerOutput)
    {
        //replace the default untitled tab with student's submission for the first time
        if (getTabCount() > 0 && getModel().getTab(0).getText().equals("Untitled"))
            getModel().removeTab(0);
        
        //create and add the tab
        final RightTab newTab = new RightTab();
        newTab.configureEditorListener(classList);
        newTab.setSubmissionInfo(submissionInfo);
        newTab.setGraderComments(comment);
        newTab.setCompilerOutput(compilerOutput);
        newTab.setFilename(submissionInfo.filename);
        TabData data = addTab(newTab, getTabCount(), submissionInfo.filename + 
                " [" + submissionInfo.username + "]", submissionInfo.username, null);
        setTabMarkingScheme((markingSchemeModel != null) ? (AttributiveCellTableModel)markingSchemeModel.clone() : new AttributiveCellTableModel(), data);
        getSelectionModel().setSelectedIndex(getTabCount() - 1);
        
        //adds the test data tabs
        addDataTabs(newTab);
                
        //load the code and update the tree
        SwingUtilities.invokeLater(new Runnable(){
            @Override
            public void run() {
                newTab.setText(text);
                newTab.setCaretPosition(0);
                classList.regenerateTree(text);
                modManager.setOriginalText(newTab, text);
                if (modManager.getOldText(newTab) == null)
                    modManager.setOldText(newTab, text);
                modManager.setOriginalComment(newTab, newTab.getCommentsField().getText());
                modManager.refresh(newTab);
            }
        });
        
        modManager.trackTab(newTab, data, getModel());
        
        return newTab;
    }
    
    private void addDataTabs(RightTab newTab)
    {
        TabbedContainer inputPane =  newTab.getInputTabbedPane();
        TabDataModel model = inputPane.getModel();
        
        for (int i = 0; i < inputTestData.size(); i+=2)
        {
            JScrollPane scrollPane = new JScrollPane();
            JTextArea textArea = new JTextArea(inputTestData.get(i + 1));
            scrollPane.setViewportView(textArea);
            String filename = inputTestData.get(i);
            TabData data = new TabData(scrollPane, null, filename, filename);
            model.addTab(inputPane.getTabCount(), data);
        }
        
        
        TabbedContainer outputPane =  newTab.getOutputTabbedPane();
        TabDataModel outModel = outputPane.getModel();
        
        for (int i = 0; i < outputTestData.size(); i+=2)
        {
            JScrollPane scrollPane = new JScrollPane();
            JTextArea textArea = new JTextArea(outputTestData.get(i + 1));
            scrollPane.setViewportView(textArea);
            String filename = outputTestData.get(i);
            TabData data = new TabData(scrollPane, null, filename, filename);
            outModel.addTab(outputPane.getTabCount(), data);
        }
    }
    
    private void setTabMarkingScheme(AttributiveCellTableModel model, final TabData tabData)
    {
        final RightTab newTab = (RightTab)tabData.getComponent();
        newTab.setMarkingScheme(model);
        newTab.setDisplayTotal(displayTotal);
        newTab.addTableChangeListener(new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {                
                //check whether value input is valid
                TableCellListener listener = (TableCellListener)e.getSource();
                Object val = listener.getNewValue();
                if (val != null && !"".equals(val))
                {
                    ColoredCell cell = (ColoredCell)(newTab.getMarkingScheme()).getCellAttribute();
                    try {
                        Double.parseDouble(val.toString());
                        cell.setForeground(MarkingScheme.foregroundNormal, listener.getRow(), listener.getColumn());
                    }
                    catch (NumberFormatException exp) {
                        cell.setForeground(MarkingScheme.foregroundError, listener.getRow(), listener.getColumn());
                        Logging.log(exp);
                    }
                }
                
                //updates the total
                if (displayTotal)
                {
                    AttributiveCellTableModel tableModel = newTab.getMarkingScheme();
                    double total = 0;
                    int row = tableModel.getRowCount() - 1;
                    int col = tableModel.getColumnCount() - 1;
                    
                    for (int i = 0; i < col; i++)
                    {
                        try {
                            Object currVal = tableModel.getValueAt(row, i);
                            if (currVal != null && !"".equals(currVal.toString()))
                                total += Double.parseDouble(currVal.toString());
                        }
                        catch (NumberFormatException exp) {
                            Logging.log(exp);
                            total = 0;
                        }
                    }
                    
                    tableModel.setValueAt(Misc.DECIMAL_FORMAT.format(total), row, col);
                }
            }
        });
    }
    
    /**
     * Make the tab having the given sid come to focus.
     * @param sid 
     */
    public void focusTab(SubmissionInfo sub)
    {
        int index = -1;
        List<TabData> list = getModel().getTabs();
        
        for (int i = 0; i < list.size(); i++)
        {
            RightTab curr = (RightTab)(list.get(i).getComponent());
            if (sub.equals(curr.getSubmissionInfo()))
            {
                index = i;
                break;
            }
        }
        
        getSelectionModel().setSelectedIndex(index);
        postActionEvent(new TabActionEvent(this, TabDisplayer.COMMAND_SELECT, index));
    }
    
    /**
     * Checks whether a particular submission is already
     * opened inside the editor.
     * @param sid
     * @return 
     */
    public boolean hasSubmission(SubmissionInfo subm)
    {
        for (TabData curr : getModel().getTabs())
        {
            SubmissionInfo sub = ((RightTab)(curr.getComponent())).getSubmissionInfo();
            if (subm.equals(sub))
                return true;
        }
        
        return false;
    }
    
    public TabData addTab(final RightTab tab, int location, String title, String toolTip,
            Icon icon)
    {
        final TabData tabData = new TabData(tab, icon, title, toolTip);
        getModel().addTab(location, tabData);
        return tabData;
    }
    
    public RightTab getTab(int location)
    {
        return (RightTab)(getModel().getTab(location).getUserObject());
    }
    
    public void closeTab(int index)
    {
        getModel().removeTab(index);
    }
    
    public void reset()
    {
        if (getModel().size() > 0)
            getModel().removeTabs(0, getModel().size() - 1);
        
        loadEmptyEditor();
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        if (e == null || !(e instanceof TabActionEvent) || e.getActionCommand() == null)
            return;
        
        TabActionEvent evt = (TabActionEvent)e;
        final int index = evt.getTabIndex();
        if (index < 0)
            return;
        TabData tabData = getModel().getTab(index);
        RightTab rightTab = (RightTab)(tabData.getComponent());
        
        String cmd = e.getActionCommand();
        
        if (TabDisplayer.COMMAND_SELECT.equals(cmd))
            classList.regenerateTree(rightTab.getText());
        
        else if (TabDisplayer.COMMAND_CLOSE.equals(cmd))
        {
            if (modManager.isTabModified(rightTab) && 
                        DisplayHelper.showConfirmFrame(this, "Code Modified", 
                        tabData.getText() + " has been modified. Proceed with closing?")
                        != JOptionPane.OK_OPTION)
                    evt.consume();
            else
            {
                rightTab.setAnalysisDialogVisibility(false);
                rightTab.hideDetailDialogs();

                if (RightPanel.this.getModel().size() <= 1)
                    classList.regenerateTree("");

                SwingUtilities.invokeLater(new Runnable(){
                    @Override
                    public void run()
                    {
                        int size = getTabCount();

                        //sets the focus to another tab while closing the current tab
                        if (index < size)
                        {
                            postActionEvent(new TabActionEvent(RightPanel.this, TabDisplayer.COMMAND_SELECT, index));
                            getSelectionModel().setSelectedIndex(index);
                        }

                        else if (index - 1 < size)
                        {
                            postActionEvent(new TabActionEvent(RightPanel.this, TabDisplayer.COMMAND_SELECT, index - 1));
                            getSelectionModel().setSelectedIndex(index - 1);
                        }
                    }
                });
            }
        }
        
        else if (TabDisplayer.COMMAND_CLOSE_ALL.equals(cmd))
        {
            for (int i = 0; i < getModel().size(); i++)
            {
                TabActionEvent tabEvt = new TabActionEvent(RightPanel.this, TabDisplayer.COMMAND_CLOSE, i);
                postActionEvent(tabEvt);
                if (!tabEvt.isConsumed())
                {
                    getModel().removeTab(i);
                    i--;
                }
            }            
        }
        
        else if (TabDisplayer.COMMAND_CLOSE_ALL_BUT_THIS.equals(cmd))
        {
            int removed = 0;
            for (int i = 0; i < index - removed; i++)
            {
                TabActionEvent tabEvt = new TabActionEvent(RightPanel.this, TabDisplayer.COMMAND_CLOSE, i);
                postActionEvent(tabEvt);
                if (!tabEvt.isConsumed())
                {
                    getModel().removeTab(i);
                    i--;
                    removed++;
                }
            }
            for (int i = index - removed + 1; i < getModel().size(); i++)
            {
                TabActionEvent tabEvt = new TabActionEvent(RightPanel.this, TabDisplayer.COMMAND_CLOSE, i);
                postActionEvent(tabEvt);
                if (!tabEvt.isConsumed())
                {
                    getModel().removeTab(i);
                    i--;
                }                    
            }
        }
    }
    
    class RightPanelPopupListener implements ActionListener
    {        
        @Override
        public void actionPerformed(ActionEvent evt) 
        {
            Object src = evt.getSource();
            
            int index = tabForCoordinate(popupLoc);
            
            if (src == closeItem)
            {
                TabActionEvent tabEvt = new TabActionEvent(RightPanel.this, TabDisplayer.COMMAND_CLOSE, index);
                final int selIndex = RightPanel.this.getSelectionModel().getSelectedIndex();
                RightPanel.this.postActionEvent(tabEvt);
                
                //have to close the tab manually
                if (!tabEvt.isConsumed())
                {
                    RightPanel.this.closeTab(index);
                    
                    //sets the selection back to the original one
                    if (selIndex != index)
                    {
                        SwingUtilities.invokeLater(new Runnable(){
                            @Override
                            public void run()
                            {
                                RightPanel.this.getSelectionModel().setSelectedIndex(selIndex - 1);
                                RightPanel.this.postActionEvent(new TabActionEvent(RightPanel.this, TabDisplayer.COMMAND_SELECT, selIndex - 1));
                            }
                        });
                    }
                }
            }
            
            else if (src == closeAllItem)
                RightPanel.this.postActionEvent(new TabActionEvent(RightPanel.this, TabDisplayer.COMMAND_CLOSE_ALL, 0));
            
            else if (src == closeOthersItem)
                RightPanel.this.postActionEvent(new TabActionEvent(RightPanel.this, TabDisplayer.COMMAND_CLOSE_ALL_BUT_THIS, index));
            
            else if (src == propsItem)
            {
                RightTab tab = (RightTab)(RightPanel.this.getModel().getTab(index).getComponent());
                SubmissionInfo info = tab.getSubmissionInfo();
                RightPanel.this.firePropertyChange("displayPropsDialog", null, info);
            }
            else if (src == diffItem)
            {
                JDialog dialog = new JDialog(Misc.getBaseGUI(RightPanel.this), "Compare with Original (Old <--> New)");
                DiffPane diffPane = new DiffPane();
                RightTab tab = (RightTab)(RightPanel.this.getModel().getTab(index).getComponent());
                diffPane.setContentType(tab.getContentType());
                diffPane.getEditorPane1().setText(modManager.getOldText(tab));
                diffPane.getEditorPane2().setText(tab.getText());
                //scrolls everything to the top and to the left
                diffPane.getEditorPane1().setCaretPosition(0);
                diffPane.getEditorPane2().setCaretPosition(0);
                final DiffPane temp = diffPane;
                new SwingWorker<Void, Void>() {
                    @Override
                    protected Void doInBackground() throws Exception {
                        Thread.sleep(25);
                        SwingUtilities.invokeLater(new Runnable(){
                            public void run()
                            {
                                javax.swing.JScrollBar bar = temp.getScrollPane1().getHorizontalScrollBar();
                                bar.setValue(bar.getMaximum() - bar.getModel().getExtent());
                            }
                        });
                        return null;
                    }
                }.execute();
                dialog.add(diffPane);
                dialog.pack();
                dialog.setVisible(true);
            }
        }
    }
}
